package board.grid;

import java.util.ArrayList;
import java.util.Collection;
/** A simple object used to represent the
 *  address of the cells on the grid.  These
 *  objects are immutable, and if two spaces
 *  storing the same informations, because of
 *  the cache system, they will probably be ==.  
 *  
 *  The cache system is important, because many
 *  instances could be created very quickly, but
 *  many of these might not be used.  To save
 *  RAM, all of the spaces created by <code>get(int, int)</code>
 *  are saved, and returned if the same space is
 *  requested.  
 *  
 *  Space has not public of protected constructors,
 *  and to get a Space, the factory method <code>get(int, int)</code>
 *  is used.  This ensures that RAM usage is minimal.  
 * 
 * @author R.Wisniewski1101
 */
public final class Space implements Comparable<Space> {
	/** The cache storing all of the instances
	 *  created with <code>get(int, int)</code>.  
	 */
	private static Collection<Space> cache = new ArrayList<Space>();
	/** The y coordinate of the space.  
	 *  Final because Space instances are immutable.  
	 */
	private final int row;
	/** The x coordinate of the space.  
	 *  Final because Space instances are immutable.  
	 */
	private final int column;
	/** Private constructor for Space objects.
	 *  Should only be used by <code>get(int, int)</code>.  
	 * 
	 * @param column - position corresponding to x.  
	 * @param row - position corresponding to y.  
	 */
	private Space(int column, int row) {
		this.row = row;
		this.column = column;
	}
	/** Get the column of the current space.  
	 *  This corresponds the the x component.  
	 * 
	 * @return the column number
	 */
	public int getColumn() {return column;}
	/** Get the row of the current space.  
	 *  This corresponds to the y component.  
	 * 
	 * @return the row number
	 */
	public int getRow() {return row;}
	/** Get all of the locations that would
	 *  in a one cell radius.  
	 * 
	 * @return the surrounding cells.  
	 */
	public Collection<Space> getSurroundingSpaces() {return getSurroundingSpaces(1);}
	/** Get all of the cells within a given
	 *  radius.  This is done by getting all
	 *  of the spaces from whose row and column
	 *  are in the range <code>this - radius <= x <= this + radius</code>.
	 * 
	 * @param radius - the distance from this space in which to return.  
	 * @return all of the spaces with in this radius
	 */
	public Collection<Space> getSurroundingSpaces(int radius) {
		ArrayList<Space> spaces = new ArrayList<Space>();
		for(int y = column - radius; y <= column + radius; ++y)
			for(int x = row - radius; x <= row + radius; ++x)
				if(x != row && y != column)
					spaces.add(get(x, y));
		return spaces;
	}
	/** Get the space directly in the direction
	 *  by one space.  
	 * 
	 * @param direction - the direction of the space.  
	 * @return the requested space
	 */
	public Space getSpaceInDirection(Direction direction) 
		{return getSpaceInDirection(direction, 1);}
	/** Get the space in the given direction
	 *  a given distance.  
	 * 
	 * @param direction - the direction of the space
	 * @param radius - the distance to the space
	 * @return - the requested space
	 */
	public Space getSpaceInDirection(Direction direction, int radius) {
		return getSpaceInRelation(
				direction.getDeltaX() * radius,  //X component
				direction.getDeltaY() * radius); //y component
	}
	/** Get the space with the give difference
	 *  in row and column values.  
	 * 
	 * @param deltaX - the change in column value
	 * @param deltaY - the change in row value
	 * @return - the requested space
	 */
	public Space getSpaceInRelation(int deltaX, int deltaY) 
		{return get(column + deltaX, row + deltaY);}
	/** Get the direction from the current
	 *  space to the specified one.  
	 * 
	 * @param target - the space to get the direction toward
	 * @return the direction facing the specified space
	 */
	public Direction getDirectionTo(Space target) {
		int dx = target.column - column;
		int dy = target.row - row;
		//if the change is 0,0, then the spaces are the same.  
		return dx == 0 && dy == 0 ?
				Direction.NONE :
					//calculate the angle measure from the x any y change
					//convert that angle to a direction instance.  
				Direction.fromDegrees((int) Math.toDegrees(Math.atan2(-dy, dx)));
	}
	@Override //Comparable
	public int compareTo(Space arg0) {
		//check the rows first
		if(row < arg0.row)
			return -1;
		else if (row > arg0.row)
			return 1;
		//if in the same row, check column
		else if (column < arg0.column)
			return -1;
		else if (column > arg0.column)
			return 1;
		//row and column are the same
		else
			return 0;
	}
	@Override //Object
	public boolean equals(Object arg0) {
		if(arg0 instanceof Space) {
			Space other = (Space) arg0;
			//check for equality
			return row == other.row && column == other.column;
		//arg0 is not a Space, so can't be same
		} else return false;
	}
	@Override //Object
	public String toString() 
			{return "("+ column + ", " + row + ")"; }
	/** Get a Space with the given column and
	 *  row.  If that combination has already
	 *  been used, then a reference to the
	 *  instance returned previously is again
	 *  returned.  Therefore, the spaces returned
	 *  by this methods can use the <code>==</code>
	 *  operator to check equality, as well as
	 *  <code>equals(Object)</code>.  
	 * 
	 * @param column - the column of the requested space
	 * @param row - the row of the requested space
	 * @return - a space object the requested values
	 */
	public static Space get(int column, int row) {
		//search cache for existing instance
		for(Space s : cache)
			if(s.getColumn() == column && s.getRow() == row)
				return s;
		//only done if not already created and cached.  
		Space space = new Space(column, row);
		//cache space for future use.  
		cache.add(space);
		return space;
	}
}
